home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 12 - 1996 / 12.12 Dec 96 / Async I⁄O Code / Sources / LabMaker.c < prev    next >
Encoding:
Text File  |  1995-11-07  |  31.9 KB  |  1,038 lines  |  [TEXT/R*ch]

  1. // File: LabMaker.c
  2.  
  3. #undef DEBUG
  4.  
  5. #include <stdio.h>
  6. #include <ctype.h>
  7. #include <string.h>
  8. #include "LabMaker.h"
  9.     
  10. // --- Global Variables
  11. CommandEntry    gCommandTable[] = {
  12.     { "comment", 3, DoComment, kPassParams},    // Usage: $comment <comment-delim> [<comment-delim>]
  13.                                                 //          LabMaker finds the comment delimiters around the 
  14.                                                 //          directive and uses these to detect comments in the file.
  15.     { "create",     2, DoCreate, kPassParams},        // Usage: ($create <lab-name> [in <directory-name>])+ [from <lab-name>]
  16.                                                 //           Bind a lab name to a directory name, creating the
  17.                                                 //          directory if it doesn't exist, and begin writing 
  18.                                                 //          the current file to the lab directory. If the "from"
  19.                                                 //          keyword is specified, copy from the specified lab
  20.                                                 //          into the new lab file before adding any lines.
  21.     { "include", 3, DoInclude, kPassParams},    // Usage: $include <file-name>
  22.                                                 //          reads in configuration file
  23.     { "insert", 3, DoInsert, kPassBuffer},        // Usage: $insert <lab-name>[ <lab-name>]* data <text to replace this comment with>
  24.     { "copy",     3, DoCopy, kPassParams},        // Usage: $copy <lab-name>[ <lab-name>]*
  25.                                                 //          Begins copying lines into the specified labs (including
  26.                                                 //          this line if it contains more than just a LabMaker
  27.                                                 //          command.
  28.     { "skip",     2, DoSkip, kPassParams},        // Usage: $skip <lab-name>[ <lab-name>]*
  29.                                                 //          Stop copying lines (inclusive) into the specified labs.
  30.     { "push",     2, DoPush, kNoParams},            // Usage: $push
  31.                                                 //          Remember the current set of active labs.
  32.     { "pop",     2, DoPop, kNoParams},            // Usage: $pop
  33.                                                 //          Recall a pushed set of labs
  34.     { "suffix",     1, DoSuffix, kPassParams},        // Usage: $suffix <filename>|<file-suffix>
  35.                                                 //          Beginning of a "suffix definition" block which lists
  36.                                                 //          the options for a particular type of file.
  37.     { "delimiter", 1, DoDelimiter, kPassParams},// Usage: $delimiter <delim-character>
  38.                                                 //          Use at the start of a file to change the command prefix
  39.                                                 //          character from "$" to something else
  40.     { "reset",     1, DoReset, kNoParams},        // Usage: $reset
  41.                                                 //          Discard the list of labs and reset all parameters
  42.                                                 //          to their default settings
  43.     { "only",     1, DoOnly, kPassParams},        // Usage: $only <lab-name>[ <lab-name>]*
  44.                                                 //          Only include this line in the specified list of labs.
  45.     { "not",     1, DoNot, kPassParams},        // Usage: $not <lab-name>[ <lab-name>]*
  46.     { "end-suffix",     1, DoEndSuffix, kNoParams}, // Usage: $end-suffix
  47.                                                 //          Reset comment and command characters to defaults
  48.     { "skip-blanks", 5, DoSkipBlanks, kNoParams}// Usage: $skip-blanks
  49.                                                 //          Ignore blank lines in this file
  50. };
  51.  
  52. enum { kCommentCommand = 0 };
  53.  
  54. short            gNumCommands = 15;
  55.  
  56. short             gAutoPop;
  57. CurrentState    gStateStack[kStackSize];
  58. short            gStackPointer;
  59.  
  60. short            gNumFileOptions;
  61. FileOptions        gAllFileOptions[kMaxFileTypes];
  62. FileOptions        gDefaultFileOptions;
  63. CurrentState    gCurrState;
  64.  
  65. // Compatibility macros for code written before I added gCurrState and Push/Pop
  66. #define gCurrFilename        (gCurrState.currFilename)
  67. #define gCurrFileOptions    (gCurrState.currFileOptions)
  68. #define gNumLabs            (gCurrState.numLabs)
  69. #define gLabs                (gCurrState.labs)
  70. #define gCurrBuffer            (gCurrState.buffer)
  71.  
  72. static int ProcessFile(char* filename, short isInclude);
  73.  
  74. // --- Public functions
  75. ProcessUserFile(char *filename)
  76. {
  77.     int        result;
  78.  
  79.     DoReset(nil, 0, 0, nil);
  80.     GetFileOptions(filename);
  81.     result = ProcessFile(filename, 0);
  82.     CloseAllLabs();
  83.     return result;
  84. }
  85.  
  86. static int ProcessFile(char* filename, short isInclude)
  87. // Internal routine for reading in a file and processing it
  88. {
  89.     FILE    *f = nil;
  90.     char    buffer[kMaxLineLength];
  91.     int        inComment = 0;
  92.  
  93.     gCurrFilename = filename;
  94.     gCurrState.isInclude = isInclude;
  95.     f = fopen(filename, "r");
  96.     if (f != nil) {
  97.         fgets(buffer, kMaxLineLength, f);
  98.         for (; feof(f) == 0; fgets(buffer, kMaxLineLength, f)) {
  99.             short length = strlen(buffer);
  100.             if (buffer[length - 1] == '\n') {
  101.                 if (gCurrState.skipBlanks) 
  102.                     continue;                // Ignore blank lines
  103.                 else
  104.                     buffer[length - 1] = ' ';    // Replace the newline with a space to simplify later processing
  105.             }
  106.             ProcessLine(buffer, &inComment, filename);
  107.         }
  108.         fclose(f);
  109.         return 0;
  110.     }
  111.     return -1;
  112. }
  113.  
  114.  
  115. void ProcessLine(char *buffer, int *inComment, char* filename)
  116. // Take one line from the file and process it. If the line contains a comment with a command,
  117. // execute the command and delete the comment.
  118. {
  119.     char    *commentStart;
  120.     short    index;
  121.     
  122.     // Start at the beginning of the buffer and find any comment string
  123.     char    result[kMaxLineLength];
  124.     short    startOfComment, endOfComment;
  125.     short    commentKind;
  126.     
  127.     strcpy(gCurrBuffer, buffer);
  128.     
  129.     if (gAutoPop) {            // Set by commands that only operate on a single line --
  130.         Pop(kReplaceLabs);    // they call Push before changing the active labs list, 
  131.         gAutoPop = 0;        // and expect a Pop to happen before the next line.
  132.     }
  133.     
  134. #ifdef DEBUG
  135.     printf("Processing: %s\n", buffer);
  136. #endif
  137.  
  138.     index = 0;
  139.     for (;;) {
  140.         // If we're not in a comment, look for the start of one
  141.         if (!*inComment && 
  142.             ((commentStart = FindCommentStart(buffer, &index, &startOfComment, &commentKind)) != NULL))
  143.             *inComment = 1;
  144.         
  145.         if (!*inComment) {
  146.             break;    // Go ahead and distribute the line
  147.         } else {
  148.             // Look for a command
  149.             CommandEntry    *ce = NULL;
  150.             
  151.             // N.B. We only support single commands placed at the start of each comment
  152.             if ((GetNextSubstring(buffer, &index, result, 0) != NULL) &&
  153.                 ((ce = FindCommand(result)) != NULL)) {
  154.                 char    extractedComment[kMaxLineLength] = {'\0'};
  155.                 short    numChars, endOfCommand;
  156.                 short    paramStart, paramEnd;
  157.                   
  158.                 // Locate the end of the command and the start of the parameters
  159.                 endOfCommand = index - 1;
  160.                 SkipWhitespace(buffer, &index);
  161.                 paramStart = index;
  162.                   
  163.                 // Locate the end of the comment. After this, index will refer to
  164.                 // one place past the end of the comment and endOfComment refers
  165.                 // to the last character inside the comment
  166.                 if (!FindCommentEnd(buffer, &index, &endOfComment, commentKind)) {
  167.                     Warning("Commands found in unterminated comments are not executed", buffer, startOfComment, filename);
  168.                     break;    // Unterminated comment -- go ahead and distribute the line
  169.                  }
  170.                   
  171.                 // See what the command wants: no parameters, the whole comment, or the
  172.                 // parameter list only
  173.                 if (ce->options == kPassBuffer) {
  174.                     // Pass the whole buffer, which may be modified by the command
  175.                     paramStart = startOfComment;    // We have to know where the comment starts so we can remove it
  176.                     paramEnd = index;
  177.                     ce->theCommand(buffer, paramStart, paramEnd, filename);
  178.                     // The command is responsible for removing the comment from the buffer,
  179.                     // so recycle to the start of the comment and process whatever remains
  180.                     index = startOfComment;
  181.                     *inComment = 0;
  182.                     continue;
  183.                 } else if (ce->options == kNoParams) {
  184.                       paramStart = paramEnd = 0;    // Call the command without parameters
  185.                 }
  186.                 else if (ce->options == kPassComment) {
  187.                     // Copy out the section of the buffer in [startOfComment, index)
  188.                     // which will give us the whole comment.
  189.                       numChars = index - startOfComment;
  190.                       strncpy(extractedComment, buffer + startOfComment, numChars);
  191.                       extractedComment[numChars] = '\0';    // strncpy doesn't always add a trailing null
  192.                       // Create references to the start and the end of the parameter list
  193.                       paramStart -= startOfComment;
  194.                       paramEnd   = endOfComment - startOfComment;
  195.                 }
  196.                 else if (ce->options == kPassParams) {
  197.                       // Copy out the section of the buffer between endOfCommand and endOfComment
  198.                       // which will give us only the parameters.
  199.                       numChars = endOfComment - paramStart + 1;
  200.                       strncpy(extractedComment, buffer + paramStart, numChars);
  201.                       extractedComment[numChars] = '\0';    // strncpy doesn't always add a trailing null
  202.                       // Create references to the start and the end of the parameter list
  203. #ifdef DEBUG
  204.                     printf("Extracted “%s”\n", extractedComment);
  205. #endif
  206.                       paramStart = 0;
  207.                       paramEnd   = numChars;
  208.                 }
  209.                   
  210.                 // Execute the command
  211.                 ce->theCommand(extractedComment, paramStart, paramEnd, filename);
  212.                   
  213.                 // Remove the entire comment from the buffer
  214.                 *inComment = 0;
  215.                 RemoveComment(buffer, &index, startOfComment);
  216.             } else {
  217.                 // No command was found in this comment, so find the end
  218. #ifdef DEBUG
  219.                 printf("*No command found\n");
  220. #endif
  221.                 if (FindCommentEnd(buffer, &index, &endOfComment, commentKind)) {
  222.                     *inComment = 0;
  223. #ifdef DEBUG
  224.                     printf("*Found end of comment\n");
  225. #endif
  226.                 } else
  227.                     break;        // Unterminated comment -- go ahead and distribute the line
  228.             }
  229.         }
  230.     }
  231.     
  232.     DistributeLine(buffer);        // Copy this line to the active labs
  233. }
  234.  
  235.  
  236. void RemoveComment(char *buffer, short *index, short startOfComment)
  237. // Delete a comment from the buffer. Index is as returned by FindCommentEnd
  238. // (i.e. one past the end delimiter); startOfComment is as returned by
  239. // FondCommentStart.
  240. {
  241.     short    numChars, newLen;
  242.     numChars = strlen(buffer) - *index;
  243.     memmove(&buffer[startOfComment], &buffer[*index], numChars);
  244.     newLen = startOfComment + numChars;
  245.     buffer[newLen] = '\0'; // memmove won't null terminate the new string, so we will
  246.     *index = startOfComment;
  247. }
  248.  
  249.  
  250. CommandEntry *FindCommand(char *possibleCommand)
  251. // We have what could be the start of a command
  252. // so check our command table for a matching entry
  253. {
  254.     short    commandNum;
  255.     
  256.     MakeLower(possibleCommand);            // Move to lowercase so we aren't case sensitive
  257.  
  258. #ifdef DEBUG
  259.             printf("Looking for command in: “%s”\n", possibleCommand);
  260. #endif
  261.  
  262.     if (*possibleCommand == gCurrFileOptions->commandChar)
  263.         possibleCommand++;
  264.     
  265.     for (commandNum = 0; commandNum < gNumCommands; commandNum++) {
  266.         CommandEntry    *currCommand = &gCommandTable[commandNum];
  267.         if (strncmp(possibleCommand, currCommand->commandName, currCommand->uniqueChars) == 0) {
  268. #ifdef DEBUG
  269.             printf("Found command: %s\n", possibleCommand);
  270. #endif
  271.             return currCommand;
  272.         }
  273.     }
  274.     
  275.     return NULL;
  276. }
  277.  
  278.  
  279. short FindLab(char* labName)
  280. // Returns kLabNotFound (-1) if the lab couldn't be found
  281. // Otherwise returns kAllLabs (-2) or the index number of the
  282. // lab in the gLabs table.
  283. {
  284.     short    labNum;
  285.     char    localLabName[kMaxLabNameLength],
  286.             globalLabName[kMaxLabNameLength];
  287.     
  288. #ifdef DEBUG
  289.     printf("Looking for the lab named: %s\n", labName);
  290. #endif
  291.  
  292.     strcpy(localLabName, labName);
  293.     MakeLower(localLabName);
  294.     
  295.     if (strcmp(localLabName, "all") == 0) 
  296.         return kAllLabs;
  297.  
  298.     for (labNum = 0; labNum < gNumLabs; labNum++) {
  299.         // Make a local lowercase copy of the lab name so we don't penalize
  300.         // the user for typing case errors
  301.         strcpy(globalLabName, gLabs[labNum].labName);
  302.         MakeLower(globalLabName);
  303.         if (strcmp(localLabName, globalLabName) == 0)
  304.             return labNum;
  305.     }
  306.     
  307.     return kLabNotFound;
  308. }
  309.  
  310.  
  311. short FindSuffix(char* filename)
  312. // Returns kSuffixNotFound (-1) if the suffix couldn't be found
  313. // Otherwise returns the index number of the suffix in gAllFileOptions.
  314. {
  315.     short    index;
  316.     char    localSuffix[kMaxLabNameLength];
  317.     char    *found;
  318.     
  319.     if (gNumFileOptions > 0) {
  320.         MakeLower(filename);
  321.         for (index = 0; index < gNumFileOptions; index++) {
  322.             // Make a local lowercase copy of the suffix so we don't penalize
  323.             // the user for typing case errors
  324.             strcpy(localSuffix, gAllFileOptions[index].suffix);
  325.             MakeLower(localSuffix);
  326.             if ((found = strstr(filename, localSuffix)) != NULL) {
  327.                 // Confirm that the suffix is at the end of the string
  328.                 if (strlen(localSuffix) == strlen(found))
  329.                     return index;
  330.             }
  331.         }
  332.     }
  333.     return kSuffixNotFound;
  334. }
  335.  
  336.  
  337. char *FindCommentStart(char *buffer, short *index, short *startOffset, short *commentKind)
  338. // Locate the beginning of a comment and let us know where it starts (startOffset) and set
  339. // index to the character after the comment delimiter
  340. {
  341.     char    *startPoint = &buffer[*index];
  342.     char    *foundPoint = NULL;
  343.     short    comType;                        // Index into the table of comment types
  344.     
  345.     // To see if we can find the start of a comment, do the following:
  346.     // 1. Scan the comment string for the first character of each comment delimiter
  347.     // 2. If the first character is found, test the rest of the characters of
  348.     //      the delimiter against the found point.
  349.     for (comType = 0; comType < kMaxCommentTypes; comType++) {
  350.         char    *testComment = gCurrFileOptions->commentDelimiters[comType].commentStart;
  351.         
  352. #ifdef DEBUG
  353. //            printf("%s\n", testComment);
  354. #endif
  355.             if (*testComment != '\0') {
  356.             foundPoint = strchr(startPoint, *testComment);
  357.             if (foundPoint != NULL) {
  358.                 short limit = strlen(testComment);
  359.                 if (strncmp(foundPoint, testComment, limit) == 0) {
  360.                     // Compute the offsets to the beginning and end of the comment
  361.                     *startOffset = (unsigned int)foundPoint - (unsigned int)buffer;
  362.                     *index = (unsigned int)*startOffset + (unsigned int)limit;
  363.                     *commentKind = comType;
  364.                     break;    // Jump to "return foundPoint"
  365.                 }
  366.             }
  367.         }
  368.     }
  369.     return foundPoint;
  370. }
  371.  
  372.  
  373. char *FindCommentEnd(char *buffer, short *index, short *endOffset, short commentKind)
  374. // Locate the end of a specific comment type. Set endOffset to the last character
  375. // contained within the comment and set index to the character after the comment delimiter
  376. {
  377.     char    *startPoint = &buffer[*index];
  378.     char    *foundPoint = NULL;
  379.     
  380.     // To see if we can find the end of a comment, do the following:
  381.     // 1. Scan the comment string for the first character the comment delimiter
  382.     // 2. If the first character is fiound, test the rest of the characters of
  383.     //      the delimiter against the found point.
  384.     char    *testComment = gCurrFileOptions->commentDelimiters[commentKind].commentEnd;
  385.     
  386.     if (*testComment == '\0') {
  387.         // This comment goes to the end of the line
  388.         *endOffset = strlen(buffer) - 1;
  389.         *index = strlen(buffer);
  390.         foundPoint = &buffer[*endOffset];
  391.     } else {
  392.         // We have an end delimiter, so search for it
  393.         foundPoint = strstr(startPoint, testComment);
  394.         if (foundPoint != NULL) {
  395.             // Compute the offsets to the beginning and end of the comment delimiter
  396.             *endOffset = (unsigned int)foundPoint - (unsigned int)buffer - 1;
  397.             *index = (unsigned int)*endOffset + (unsigned int)strlen(testComment) + 1;
  398.         }
  399.     }
  400.     return foundPoint;
  401. }
  402.  
  403.  
  404. char *SkipWhitespace(char *buffer, short *index)
  405. // Skip over any spaces or tabs in the buffer beginning at *index
  406. {
  407.     short    localIndex = *index;
  408.     char    *currChar = &buffer[localIndex];
  409.     char    ch;
  410.     
  411.     ch = *currChar;
  412.     while ((ch != '\0') && (ch == ' ') || (ch == '\t')) {
  413.         currChar++; localIndex++;
  414.         ch = *currChar;
  415.     }
  416.     
  417.     *index = localIndex;
  418.     if (currChar == '\0') {
  419.         return NULL;
  420.     } else {
  421.         return &buffer[localIndex];
  422.     }
  423. }
  424.  
  425.  
  426. void DistributeLine(char *buffer)
  427. // Write the current buffer to this file in all of the active labs
  428. {
  429.     short    labID;
  430.     
  431.     if ((gNumLabs > 0) && (*buffer != '\0')) {
  432.         for (labID = 0; labID < gNumLabs; labID++) {
  433.             if (gLabs[labID].isActive) {
  434.                 fputs(buffer, gLabs[labID].file);
  435.                 fputc('\n', gLabs[labID].file);
  436.             }
  437.         }
  438.     }
  439. }
  440.  
  441.  
  442. static char *MakeLower(char* buffer)
  443. // Convert the entire string in buffer to lowercase.
  444. // The buffer is modified
  445. {
  446.     char    *currChar;
  447.     for (currChar = buffer; *currChar != '\0'; currChar++) {
  448.         *currChar = tolower(*currChar);
  449.     }
  450.     return buffer;
  451. }
  452.  
  453.  
  454. char *GetKeyword(char* keyword, char* buffer, short *index, char* result)
  455. // Extract the next token in the buffer and see if it matches the specified
  456. // keyword
  457. {
  458.     short    indexCopy = *index;
  459.     short    startIndex = *index;
  460.     
  461.     if (GetNextSubstring(buffer, &indexCopy, result, 0) != NULL) {
  462.         char *currChar;
  463.         // Convert the string into lowercase for comparison purposes
  464.         for (currChar = result; *currChar != '\0'; currChar++) {
  465.             *currChar = tolower(*currChar);
  466.         }
  467.         
  468.         // Do the strings match?
  469.         if (strcmp(result, keyword) == 0) {
  470.             *index = indexCopy;
  471.             return &buffer[startIndex];
  472.         }
  473.     }
  474.     return NULL;    // No matching keyword found
  475. }
  476.  
  477.  
  478. char *GetNextSubstring(char* buffer, short *index, char *result, short allowQuotes)
  479. {
  480.     // Extract a lab name (possibly quoted) from the buffer
  481.     char    quote = '\0';
  482.     char    *currChar, ch;
  483.     short    start;
  484.     short    startIndex;
  485.     
  486.     currChar = SkipWhitespace(buffer, index);
  487.     if (currChar == NULL) {
  488.         *result = '\0';
  489.         return NULL;
  490.     };
  491.     
  492.     startIndex = *index;
  493.     
  494.     // Get any leading quote
  495.     if (allowQuotes && (*currChar == '"') || (*currChar == '\'')) {
  496.         quote = *currChar;
  497.         currChar++; *index += 1;
  498.     }
  499.     start = *index;
  500.     
  501.     // Scan to the end of the string
  502.     if (quote != '\0') {
  503.         // If we have a quote, it ends the string
  504.         ch = *currChar;
  505.         while ((ch != '\0') && (ch != quote)) {
  506.             currChar++;  *index += 1;
  507.             ch = *currChar;
  508.         }
  509.     } else {
  510.         // No quotation marks, so a space or tab ends the string\
  511.         // If this is a possible lab name, a comma also ends the name.
  512.         ch = *currChar;
  513.         while ((ch != '\0') && (ch != '\n') && (ch != ' ') && (ch != '\t')) {
  514.             currChar++;  *index += 1;
  515.             ch = *currChar;
  516.         }
  517.     }
  518.     
  519.     if (start < *index) {
  520.         short numChars = *index - start;           // Don't copy the trailing space, thus no "+1"
  521.         strncpy(result, &buffer[start], numChars); 
  522.         result[numChars] = '\0';                   // Terminate the string
  523.         return &buffer[startIndex];                   // Point to the start of the original substring
  524.     } else {
  525.         *result = '\0';
  526.         return NULL;
  527.     }
  528. }
  529.  
  530.  
  531. char *GetLabName(char* buffer, short *index, char *result)
  532. {
  533.     return GetNextSubstring(buffer, index, result, 1);
  534. }
  535.  
  536.  
  537. char *GetFileName(char* buffer, short *index, char *result)
  538. {
  539.     return GetNextSubstring(buffer, index, result, 1);
  540. }
  541.  
  542.  
  543. char *GetDirectoryName(char* buffer, short *index, char *result)
  544. {
  545.     return GetNextSubstring(buffer, index, result, 1);
  546. }
  547.  
  548.  
  549. void GetFileOptions(char *filename)
  550. {
  551. #pragma unused (filename)
  552.     short    suffixID;
  553.     
  554.     if ((suffixID = FindSuffix(filename)) != kSuffixNotFound)
  555.         gCurrState.currFileOptions = &gAllFileOptions[suffixID];
  556.     else
  557.         // Leave the current suffix rules in place
  558.         gCurrState.currFileOptions = &gDefaultFileOptions;
  559. }
  560.  
  561.  
  562. void Push(void)
  563. {
  564.     // The stack pointer always points to the next available slot
  565.     // Don't push if it would cause a stack overflow
  566.     if (gStackPointer < kStackSize)
  567.         gStateStack[gStackPointer++] = gCurrState;
  568. }
  569.  
  570.  
  571. void Pop(short options)
  572. {
  573.     // Don't pop if it would cause a stack underflow
  574.     if (gStackPointer > 0) {
  575.         CurrentState    oldState = gCurrState;
  576.         gCurrState = gStateStack[--gStackPointer];
  577.         if (options & kMergeLabs) {
  578.             // Merge in the active labs from the previous stack state
  579.             short    count, limit;
  580.     
  581.             for (count = 0, limit = oldState.numLabs; count < limit; count++) {
  582.                 gCurrState.labs[count] = oldState.labs[count];
  583.             }
  584.             gCurrState.numLabs = oldState.numLabs;     
  585.         }
  586.     }
  587. }
  588.  
  589. void OpenLab(short labID)
  590. {
  591.     CurrentState     *hostFile = &gCurrState;
  592.     short            fileID = gStackPointer;
  593.     
  594.     while (hostFile->isInclude) {
  595.         *hostFile = gStateStack[--fileID];
  596.     }
  597.  
  598.     if (gLabs[labID].file == NULL)
  599.         gLabs[labID].file = GetOrMakeFile(hostFile->currFilename, gLabs[labID].directoryName, 0);
  600. }
  601.  
  602.  
  603. void CloseLab(short labID)
  604. {
  605.     if (gLabs[labID].file != NULL)
  606.         fclose(gLabs[labID].file);
  607.     gLabs[labID].file = NULL;
  608. }
  609.  
  610.  
  611. void CloseAllLabs(void)
  612. {
  613.     short    labID;
  614.     
  615.     if (gNumLabs > 0) {
  616.         for (labID = 0; labID < gNumLabs; labID++)
  617.             CloseLab(labID);
  618.     }
  619. }
  620.  
  621.  
  622. void CopyOrSkip(char *buffer, short paramStart, short paramEnd, char* filename, short activate)
  623. {
  624. #pragma unused (paramEnd)
  625.     // Collect the supplied list of labs and activate or deactivate them
  626.     char    result[kMaxLineLength];
  627.     short    index;
  628.     
  629.     if (gNumLabs == 0) return;
  630.     
  631.     index = paramStart;
  632.     while (GetLabName(buffer, &index, result)) {
  633.         short    labID;
  634.         
  635.         labID = FindLab(result);
  636.         if (labID >= 0) 
  637.             gLabs[labID].isActive = activate;
  638.         else {
  639.             if (labID == kLabNotFound)
  640.                 Error("This lab name hasn't been defined", buffer, index, filename);
  641.             else if (labID == kAllLabs) {
  642.                 // Enable or disable all labs
  643.                 short count, limit;
  644.                 limit = gNumLabs;
  645.                 
  646.                 for (count = 0; count < limit; count++)
  647.                     gLabs[count].isActive = activate;
  648.                 
  649.                 break;    // No use processing any more labs after "all"
  650.             }
  651.         }
  652.     }
  653. }
  654.  
  655.  
  656. void ClearFileOption(FileOptions *oneOption)
  657. // This is called when creating a new file options block. The command character is set to our
  658. // default ($), everything else is unset.
  659. {
  660.     short    count;
  661.     
  662.     *(oneOption->suffix) = '\0';
  663.     oneOption->numCommentTypes = 0;    // Activates // comments until replaced
  664.     strcpy(oneOption->commentDelimiters[0].commentStart, "//");
  665.     *(oneOption->commentDelimiters[0].commentEnd) = '\0';
  666.     for (count = 1; count < kMaxCommentTypes; count++) {
  667.         *(oneOption->commentDelimiters[count].commentStart) = '\0';
  668.         *(oneOption->commentDelimiters[count].commentEnd) = '\0';
  669.     }
  670.     oneOption->commandChar = '$';
  671. }
  672.  
  673.  
  674. // ---------------------------------------------------------------------------------
  675. //                Functions which implement LabMaker's commands
  676. // ---------------------------------------------------------------------------------
  677.  
  678. void DoInsert(char *buffer, short paramStart, short paramEnd, char* filename)
  679. // Usage: $insert <lab-name>[ <lab-name>]* data <text to replace this comment with>
  680. {
  681. #pragma unused (paramEnd)
  682.     char    *dataKeyword;
  683.     short    index = paramStart;
  684.     short    commentType, commentStart, commentEnd;
  685.     char    result[kMaxLineLength];
  686.     char    replacement[kMaxLineLength];
  687.     char    endOfString[kMaxLineLength];
  688.     char    bufferCopy[kMaxLineLength];
  689.     
  690.     // We've been passed the entire buffer which we can modify as desired
  691.     // paramStart points to the start of the comment, paramEnd to the end
  692.     Push();
  693.     gAutoPop = 1;
  694.     
  695.     if (gNumLabs >= 0) {
  696.         // Deactivate all labs
  697.         short count, limit;
  698.         limit = gNumLabs;
  699.                 
  700.         for (count = 0; count < limit; count++)
  701.             gLabs[count].isActive = 0;
  702.  
  703.         strcpy(bufferCopy, buffer);
  704.         // Locate the start and end of the comment and the start of the parameters
  705.         FindCommentStart(bufferCopy, &index, &commentStart, &commentType);
  706.         GetNextSubstring(bufferCopy, &index, result, 0);            // Skip the command
  707.         SkipWhitespace(bufferCopy, &index);
  708.         paramStart = index;
  709.         
  710.         // Locate the "data" keyword
  711.         dataKeyword = strstr(bufferCopy, "data");
  712.         while (dataKeyword != NULL) {
  713.             char*    beforeKeyword, *afterKeyword;
  714.             beforeKeyword =(char*)(dataKeyword - 1);
  715.             afterKeyword =(char*)(dataKeyword + 4);
  716.             // Make sure this word stands alone
  717.             if ((*beforeKeyword != ' ') && (*beforeKeyword != '\t') &&
  718.                 (*afterKeyword != ' ') && (*afterKeyword != '\t')) {
  719.                 dataKeyword = strstr(afterKeyword, "data");
  720.                 continue;
  721.             } else {
  722.                 break;
  723.             }
  724.         }
  725.         FindCommentEnd(bufferCopy, &index, &commentEnd, commentType);
  726.     
  727.         // Extract the list of labs and activate each of them
  728.         if (dataKeyword != NULL) {
  729.             *dataKeyword = '\0';    // End the string just after the list of labs
  730.             strcpy(result, (char*)(bufferCopy + (unsigned long)paramStart)); 
  731.             CopyOrSkip(result, 0, strlen(result), filename, 1);
  732.         
  733.             // Copy out the data portion which will replace our comment
  734.             if (commentEnd < strlen(bufferCopy))
  735.                 bufferCopy[commentEnd] = '\0';    // Cut the string at the end of the comment
  736.             strcpy(replacement, &dataKeyword[4]);
  737.         }
  738.         // Remove the comment from the buffer and return the buffer
  739.         // to be distributed
  740.         RemoveComment(buffer, &index, commentStart);
  741.         // Insert the replacement string by splitting the buffer into "before"
  742.         // and "after" strings then concatenating the pieces together.
  743.         strcpy(endOfString, &buffer[index]);
  744.         if (index > 0)
  745.             buffer[index - 1] = '\0';
  746.         strcat(buffer, replacement);
  747.         strcat(buffer, endOfString);
  748.     }
  749. }
  750.  
  751.  
  752. void DoComment(char *buffer, short paramStart, short paramEnd, char* filename)
  753. // Usage: $comment <comment-delim> [<comment-delim>]
  754. //          LabMaker finds the comment delimiters after the 
  755. //          directive and uses these to detect comments in the file.
  756. {
  757. #pragma unused(paramEnd, filename)
  758.     // Get the first and last tokens from the string, and use these as the
  759.     // comment delimiters
  760.     char    result[kMaxLineLength];
  761.     short    index = paramStart;
  762.     
  763.     if (gCurrState.currFileOptions->numCommentTypes + 1 <= kMaxCommentTypes) {
  764.         if (GetNextSubstring(buffer, &index, result, 0)) {
  765.             // Make this the starting delimiter
  766.             short    currType = gCurrState.currFileOptions->numCommentTypes++;
  767.             strcpy(gCurrState.currFileOptions->commentDelimiters[currType].commentStart, result);
  768.             
  769.             if (GetNextSubstring(buffer, &index, result, 0)) 
  770.                 strcpy(gCurrState.currFileOptions->commentDelimiters[currType].commentEnd, result);
  771.             else
  772.                 *gCurrState.currFileOptions->commentDelimiters[currType].commentEnd = '\0';
  773.         }
  774.     }
  775. }
  776.  
  777. void DoCreate(char *buffer, short paramStart, short paramEnd, char* filename)
  778. // Usage: ($create <lab-name> [in <directory-name>])+ [from <lab-name>]
  779. //           Bind a lab name to a directory name, creating the
  780. //          directory if it doesn't exist, and begin writing 
  781. //          the current file to the lab directory. If the "from"
  782. //          keyword is specified, copy from the specified lab
  783. //          into the new lab file before adding any lines.
  784. {
  785.     char    labName[kMaxLabNameLength];
  786.     char    keyword[kMaxCommandLength];
  787.     char    directory[kMaxLabNameLength];
  788.     int        oldLabCount = gNumLabs;
  789.     int        dirOK;
  790.     short    hasFrom = 0;
  791.     short    index = paramStart;
  792.     short    indexCopy;
  793.     
  794.     // If we're processing an included file, find the original file
  795.     CurrentState     *hostFile = &gCurrState;
  796.     short            fileID = gStackPointer;
  797.     
  798.     while (hostFile->isInclude) {
  799.         *hostFile = gStateStack[--fileID];
  800.     }
  801.  
  802.     // Build the list of new labs
  803.     while (index < paramEnd) {
  804.         indexCopy = index;
  805.         if (GetKeyword("from", buffer, &indexCopy, keyword)) {
  806.             hasFrom = 1;
  807.             break;                // Exit the loop
  808.         };
  809.         
  810.         if (GetLabName(buffer, &index, labName) == NULL)
  811.             break;                // We're out of lab names in the parameter list
  812.  
  813.         // Make sure this lab isn't already represented in our list
  814.         if (FindLab(labName) != kLabNotFound) {
  815.             Error("Duplicate lab name", buffer, index, filename);
  816.             // Skip the folder name for this one lab
  817.             if (GetKeyword("in", buffer, &index, keyword))
  818.                 GetDirectoryName(buffer, &index,  directory);
  819.         } else {
  820.             // Copy the lab name into our list of labs
  821.             strcpy(gLabs[gNumLabs].labName, labName);
  822.             // See if the user specified a directory name for this lab
  823.             dirOK = 0;
  824.             if (GetKeyword("in", buffer, &index, keyword)) {
  825.                 // We have the "in" keyword so extract the directory name
  826.                 if (GetDirectoryName(buffer, &index,  directory)) {
  827.                     strcpy(gLabs[gNumLabs].directoryName, directory);
  828.                     dirOK = 1;
  829.                 } else {
  830.                     Warning("No directory name supplied, using lab name", buffer, index, filename);
  831.                 }
  832.             };
  833.             if (!dirOK) {
  834.                 // Use the lab name for the directory name
  835.                 strcpy(gLabs[gNumLabs].directoryName, labName);
  836.             }
  837.             gNumLabs++;        // Another lab added successfully
  838.         }
  839.     }
  840.     
  841.     // Set up the newly added labs
  842.     if (oldLabCount < gNumLabs) {
  843.  
  844.          short    count;
  845.          // If there's a "from" keyword, copy the specified source file to the named
  846.         // labs
  847.          if (hasFrom) {
  848.              
  849.             if (GetDirectoryName(buffer, &index,  directory)) {
  850.                  for (count = oldLabCount; count < gNumLabs; count++) {
  851.                      CopyFile(hostFile->currFilename, directory, gLabs[count].directoryName, 0);
  852.                  }
  853.             } else {
  854.                 Error("No directory name supplied", buffer, index, filename);
  855.             }
  856.         }
  857.         
  858.         // Activate the newly added labs
  859.         for (count = oldLabCount; count < gNumLabs; count++) {
  860.             GetOrMakeDirectory(gLabs[count].directoryName, 0);
  861.             OpenLab(count);
  862.             gLabs[count].isActive = 1;
  863.         }
  864.     }
  865. }
  866.  
  867.  
  868. void DoInclude(char *buffer, short paramStart, short paramEnd, char* filename)
  869. // Usage: $include <file-name>
  870. //          reads in configuration file or another file to be processed
  871. {
  872. #pragma unused (paramEnd)
  873.     char    includeFile[kFullPathLength];
  874.     short    index = paramStart;
  875.     
  876.     if (GetFileName(buffer, &index, includeFile)) {
  877.         Push();
  878.         if (ProcessFile(includeFile, 1) != 0)
  879.             Error("Couldn't open file", buffer, index, filename);
  880.         Pop(kMergeLabs);
  881.     } else
  882.         Error("No file name found", buffer, index, filename);
  883. }
  884.  
  885.  
  886. void DoCopy(char *buffer, short paramStart, short paramEnd, char* filename)
  887. // Usage: $copy <lab-name>[, <lab-name>]*
  888. //          Begins copying lines into the specified labs (including
  889. //          this line if it contains more than just a LabMaker
  890. //          command.
  891. {
  892.     CopyOrSkip(buffer, paramStart, paramEnd, filename, 1);
  893. }
  894.  
  895.  
  896. void DoSkip(char *buffer, short paramStart, short paramEnd, char* filename)
  897. // Usage: $skip <lab-name>[, <lab-name>]*
  898. //          Stop copying lines (inclusive) into the specified labs.
  899. {
  900. #pragma unused (buffer, paramStart, paramEnd, filename)
  901.     CopyOrSkip(buffer, paramStart, paramEnd, filename, 0);
  902. }
  903.  
  904.  
  905. void DoPush(char *buffer, short paramStart, short paramEnd, char* filename)
  906. // Usage: $push
  907. //          Remember the current set of active labs.
  908. {
  909. #pragma unused (buffer, paramStart, paramEnd, filename)
  910.     Push();
  911. }
  912.  
  913.  
  914. void DoPop(char *buffer, short paramStart, short paramEnd, char* filename)
  915. // Usage: $pop
  916. //          Recall a pushed set of labs
  917. {
  918. #pragma unused (buffer, paramStart, paramEnd, filename)
  919.     Pop(kReplaceLabs);
  920. }
  921.  
  922.  
  923. void DoSuffix(char *buffer, short paramStart, short paramEnd, char* filename)
  924. // Usage: $suffix <filename>|<file-suffix>
  925. //          Beginning of a "suffix definition" block which lists
  926. //          the options for a particular type of file.
  927. {
  928. #pragma unused (buffer, paramStart, paramEnd, filename)
  929.     short    suffixID;
  930.     short index = paramStart;
  931.     char    result[kMaxLineLength];
  932.     
  933.     if (GetFileName(buffer, &index, result)) {
  934.         suffixID = FindSuffix(buffer);
  935.         if (suffixID == kSuffixNotFound) {
  936.             // Add another entry to the file types table
  937.             if (gNumFileOptions + 1 == kMaxFileTypes)    { // Don't add more than the limit
  938.                 Warning("File options table full", buffer, index, filename);
  939.                 return;
  940.             }
  941.             suffixID = gNumFileOptions++;
  942.             gCurrState.currFileOptions = &gAllFileOptions[suffixID];;
  943.             ClearFileOption(gCurrState.currFileOptions);
  944.             strcpy(gCurrState.currFileOptions->suffix, result);
  945.         } else
  946.             // We already know about this suffix, so update its entry in the table
  947.             gCurrState.currFileOptions = &gAllFileOptions[suffixID];
  948.     } else
  949.         Error("File name or suffix not found", buffer, index, filename);
  950. }
  951.  
  952.  
  953. void DoDelimiter(char *buffer, short paramStart, short paramEnd, char* filename)
  954. // Usage: $delimiter <delim-character>
  955. //          Use at the start of a file to change the command prefix
  956. //          character from "$" to something else
  957. {
  958. #pragma unused (buffer, paramStart, paramEnd, filename)
  959.     short    index = paramStart;
  960.     char    result[kMaxLineLength];
  961.     
  962.     if (GetNextSubstring(buffer, &index, result, 0))    
  963.         gCurrState.currFileOptions->commandChar = *result;
  964.     else
  965.         Error("Expected a character after 'delimiter'", buffer, index, filename);
  966. }
  967.  
  968.  
  969. void DoReset(char *buffer, short paramStart, short paramEnd, char* filename)
  970. // Usage: $reset
  971. //          Discard the list of labs and reset all parameters
  972. //          to their default settings
  973. {
  974. #pragma unused (buffer, paramStart, paramEnd, filename)
  975. //    gNumLabs = 0;
  976.     gNumFileOptions = 0;
  977.     gAutoPop = 0;
  978.     gStackPointer = 0;
  979.     gCurrState.skipBlanks = 0;
  980.     // Set up default values for comment formats, command characters, etc.
  981.     // Our default command character is '$' and we support both C comment formats
  982.     *gDefaultFileOptions.suffix = '\0';
  983.     gDefaultFileOptions.numCommentTypes = 2;
  984.     gDefaultFileOptions.commandChar = '$';
  985.     strcpy(gDefaultFileOptions.commentDelimiters[0].commentStart, "/*");
  986.     strcpy(gDefaultFileOptions.commentDelimiters[0].commentEnd, "*/");
  987.     strcpy(gDefaultFileOptions.commentDelimiters[1].commentStart, "//");
  988.     *(gDefaultFileOptions.commentDelimiters[1].commentEnd) = '\0';
  989.     gCurrFileOptions = &gDefaultFileOptions;
  990. }
  991.  
  992.  
  993. void DoOnly(char *buffer, short paramStart, short paramEnd, char* filename)
  994. // Usage: $only <lab-name>[, <lab-name>]*
  995. //          Only include this line in the specified list of labs.
  996. {
  997.     Push();
  998.     gAutoPop = 1;
  999.     if (gNumLabs >= 0) {
  1000.         short count, limit;
  1001.         limit = gNumLabs;
  1002.                 
  1003.         for (count = 0; count < limit; count++)
  1004.             gLabs[count].isActive = 0;
  1005.     }
  1006.     // Activate only the specified labs
  1007.     DoCopy(buffer, paramStart, paramEnd, filename);
  1008. }
  1009.  
  1010.  
  1011. void DoNot(char *buffer, short paramStart, short paramEnd, char* filename)
  1012. // Usage: $not <lab-name>[, <lab-name>]*
  1013. //          Exclude just this line from the specified list of labs
  1014. {
  1015.     Push();
  1016.     gAutoPop = 1;
  1017.     // Deactivate only the specified labs
  1018.     DoSkip(buffer, paramStart, paramEnd, filename);
  1019. }
  1020.  
  1021. void DoEndSuffix(char *buffer, short paramStart, short paramEnd, char* filename)
  1022. // Usage: $end-suffix
  1023. // Resets our comment delimiter and command character to the default
  1024. {
  1025. #pragma unused(buffer, paramStart, paramEnd, filename)
  1026.     gCurrState.currFileOptions = &gDefaultFileOptions;
  1027. }
  1028.  
  1029.  
  1030. void DoSkipBlanks(char *buffer, short paramStart, short paramEnd, char* filename)
  1031. // Usage: $skip-blanks
  1032. // Ignore all blank lines in this file
  1033. {
  1034. #pragma unused(buffer, paramStart, paramEnd, filename)
  1035.     gCurrState.skipBlanks = 1;
  1036. }
  1037.  
  1038.